/*
 * Copyright (c) 2007 Aleksey Nikiforov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Aleksey Nikiforov' nor the names of other contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lex.input.effects;

import java.io.IOException;

import org.lex.input.effects.interpolation.GoalQuaternionInterpolation;
import org.lex.input.effects.interpolation.GoalVector3fInterpolation;

import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
import com.jme.util.export.JMEExporter;
import com.jme.util.export.JMEImporter;

public class InterpolationController extends Controller {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	
	protected Vector3f worldUp;
	
	protected Spatial controlled;
	protected GoalVector3fInterpolation posInterpolation;
	protected GoalQuaternionInterpolation rotInterpolation;
	protected Vector3f desiredPosition = new Vector3f();
	
	private Vector3f desiredLookDirection = new Vector3f();
	protected Quaternion desiredRotation = new Quaternion();
	
	protected boolean interpolatePosition;
	protected boolean interpolateRotation;
	
	/**
	 * If posInterpolation is null then no adjustments will be made to
	 * the localTranslation. If rotationInterpolation is null, then
	 * no adjustments will be made to the localRotation.
	 * 
	 * @param controlled
	 * @param posInterpolation
	 * @param rotInterpolation
	 * @param worldUp
	 */
	public InterpolationController(Spatial controlled,
			GoalVector3fInterpolation posInterpolation,
			GoalQuaternionInterpolation rotInterpolation,
			Vector3f worldUp)
	{
		this.worldUp = worldUp;
		this.posInterpolation = posInterpolation;
		posInterpolation.setAcceleratePastInitial(true);
		this.rotInterpolation = rotInterpolation;
		this.controlled = controlled;
		super.setRepeatType(Controller.RT_CLAMP);
		setActive(false);
	}
	
	public void setDesiredPosition(Vector3f pos) {
		desiredPosition.set(pos);
	}
	
	public Spatial getControlled() {
		return controlled;
	}

	public void setControlled(Spatial controlled) {
		this.controlled = controlled;
	}

	public void setDesiredLookAt(Vector3f desiredLookAt) {
		desiredLookAt.subtract(
				controlled.getLocalTranslation(), desiredLookDirection);
		if (desiredLookDirection.length() > FastMath.ZERO_TOLERANCE) {
			desiredRotation.lookAt(desiredLookDirection, worldUp);
		}
	}

	public void setDesiredLookDirection(Vector3f desiredLookDirection) {
		if (desiredLookDirection.length() > FastMath.ZERO_TOLERANCE) {
			desiredRotation.lookAt(desiredLookDirection, worldUp);
		}
	}

	public void setDesiredRotation(Quaternion desiredRotation) {
		this.desiredRotation.set(desiredRotation);
	}
	
	/**
	 * You have to ensure that spatial's localTranslation and localRotation
	 * are initilized to the values you want interpolation to start with
	 * before calling this method.
	 * Also you have to ensure that desiredPosition and desiredRotation
	 * are set before calling this method.
	 * 
	 * Calling this method will activate the controller.
	 */
	public void startInterpolation() {
		if (posInterpolation != null) {
			Vector3f pos = controlled.getLocalTranslation();
			posInterpolation.init(pos, desiredPosition);
			interpolatePosition = true;
		}
		
		if (rotInterpolation != null) {
			Quaternion rot = controlled.getLocalRotation();
			rotInterpolation.init(rot, desiredRotation);
			rotInterpolation.setAcceleratePastInitial(false);
			interpolateRotation = true;
		}
		
		setActive(interpolatePosition || interpolateRotation);
	}

	@Override
	public void update(float time) {
		if (posInterpolation != null) {
			if (interpolatePosition) {
				posInterpolation.setGoal(desiredPosition);
				posInterpolation.update(time);
				Vector3f currentPosition = posInterpolation.getCurrent();
				controlled.getLocalTranslation().set(currentPosition);
				interpolatePosition = posInterpolation.isAlive();
			} else {
				controlled.getLocalTranslation().set(desiredPosition);
			}
		}
		
		if (rotInterpolation != null) {
			if (!interpolatePosition) {
				rotInterpolation.setAcceleratePastInitial(true);
			}
			rotInterpolation.setGoal(desiredRotation);
			rotInterpolation.update(time);
			Quaternion currentRotation = rotInterpolation.getCurrent();
			controlled.getLocalRotation().set(currentRotation);
			interpolateRotation = rotInterpolation.isAlive();
		}
		
		setActive(interpolatePosition || interpolateRotation);
	}
	
	// overrides to prevent unintended use
	public void setRepeatType(int type) {
		throw new UnsupportedOperationException();
	}
	
	public void read(JMEImporter e) throws IOException {
		throw new UnsupportedOperationException();
	}
	
	 public void write(JMEExporter e) throws IOException {
		 throw new UnsupportedOperationException();
	 }

}
